package com.qingcloud.df.executor.log;

import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.IdUtil;
import com.qingcloud.df.executor.pojo.JobInstanceLog;
import com.qingcloud.df.executor.constant.DateFormatterConstant;
import com.qingcloud.df.executor.log.impl.ComponentCrateLogger;
import com.qingcloud.df.executor.log.impl.ComponentFileLogger;
import com.qingcloud.df.sdk.component.context.CompInstHelper;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.helpers.FormattingTuple;
import org.slf4j.helpers.MessageFormatter;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.LockSupport;


/**
 * 融合平台日志工具
 *
 * @author wangjinnan
 */
@Slf4j
public class DataFusionLogger {

    /**
     * 线程ID产生器
     */
    public static final Snowflake THREAD_ID_GENERATE = IdUtil.getSnowflake(1, 1);

    /**
     * 读取新增日志线程缓存
     */
    public static final Map<String, Thread> READ_AFTER_THREAD = new ConcurrentHashMap<>();

    private DataFusionLogger() {
    }

    /**
     * 日志组件，目前使用文件日志实现
     */
    private static IComponentLogger componentLogger;

    /**
     * 初始化
     */
    public static void init(String logType) {
        if (LogTypeEnum.CRATE.code.equalsIgnoreCase(logType)) {
            componentLogger = ComponentCrateLogger.getInstance();
        } else {
            componentLogger = ComponentFileLogger.getInstance();
        }
    }

    /**
     * 节点名称
     */
    public static String nodeName = "";

    /**
     * 运行几点绑定的ip
     */
    public static String bindHost = "";

    /**
     * 标准日志头部
     *
     * @return 日志头
     */
    private static String logHead() {
        return LocalDateTime.now().format(DateFormatterConstant.NORMAL_FULL_FORMATTER) + " "
                + nodeName + "-" + bindHost + " ";
    }

    /**
     * info级别日志，自动添加换行符
     *
     * @param instanceId         组件实例ID
     * @param appendLogPattern   日志表达式，类型log4j等 aaa {} bbb {} ccc"
     * @param appendLogArguments 追加的参数
     */
    public static void info(String env, Integer jobId, Integer instanceId, String softwareName, String appendLogPattern, Object... appendLogArguments) {
        log(env, "INFO", jobId, instanceId, softwareName, appendLogPattern, appendLogArguments);
    }

    /**
     * info级别日志，自动添加换行符
     *
     * @param instanceId         组件实例ID
     * @param appendLogPattern   日志表达式，类型log4j等 aaa {} bbb {} ccc"
     * @param appendLogArguments 追加的参数
     */
    public static void warn(String env, Integer jobId, Integer instanceId, String softwareName, String appendLogPattern, Object... appendLogArguments) {
        log(env, "WARN", jobId, instanceId, softwareName, appendLogPattern, appendLogArguments);
    }

    /**
     * 异常日志
     *
     * @param instanceId 组件实例ID
     * @param e          异常对象
     */
    public static void error(String env, Integer jobId, Integer instanceId, String softwareName, Throwable e) {
        StringWriter stringWriter = new StringWriter();
        e.printStackTrace(new PrintWriter(stringWriter));
        String appendLog = stringWriter.toString();
        String logContent = logHead() + "[ERROR] " + appendLog ;
        componentLogger.appendUtf8Log(jobId, instanceId, softwareName, logContent+ "\r\n", env);
        //增加了新日志，唤醒读取日志线程
        wakeReadLogThread(instanceId);
    }

    /**
     * 原始内容日志，不会添加换行符，主要作用于独立进程
     *
     * @param instanceId         组件实例ID
     * @param appendLogPattern   日志表达式，类型log4j等 aaa {} bbb {} ccc"
     * @param appendLogArguments 追加的参数
     */
    public static void raw(String env, Integer jobId, Integer instanceId, String softwareName, String appendLogPattern, Object... appendLogArguments) {
        FormattingTuple ft = MessageFormatter.arrayFormat(appendLogPattern, appendLogArguments);
        String appendLog = ft.getMessage();
        String logContent = logHead() + "[RAW] " + appendLog;
        componentLogger.appendUtf8Log(jobId, instanceId, softwareName, logContent + "\r\n", env);
        //增加了新日志，唤醒读取日志线程
        wakeReadLogThread(instanceId);
    }

    /**
     * 记录组件日志，自动添加换行符
     *
     * @param level              日志级别
     * @param instanceId         组件实例ID
     * @param appendLogPattern   日志表达式，类型log4j等 aaa {} bbb {} ccc"
     * @param appendLogArguments 追加的参数
     */
    private static void log(String env, String level, Integer jobId, Integer instanceId, String softwareName, String appendLogPattern, Object... appendLogArguments) {
        FormattingTuple ft = MessageFormatter.arrayFormat(appendLogPattern, appendLogArguments);
        String appendLog = logHead() + "[" + level + "] " + ft.getMessage();
        componentLogger.appendUtf8Log(jobId, instanceId, softwareName, appendLog + "\r\n", env);
        //增加了新日志，唤醒读取日志线程
        wakeReadLogThread(instanceId);
    }

    /**
     * 唤醒读取新日志线程
     */
    public static void wakeReadLogThread(Integer instanceId) {
        for (Map.Entry<String, Thread> entry : READ_AFTER_THREAD.entrySet()) {
            if (entry.getKey().startsWith(instanceId + "#")) {
                log.info("wake up thread {}", entry.getKey());
                LockSupport.unpark(entry.getValue());
            }
        }
    }

    /**
     * 从已经读取的日志里面向前读日志
     *
     * @param instanceId 组件实例ID
     * @param preStart   往前读从哪行开始
     * @param afterStart 往后读从哪行开始
     * @param size       每次多少行日志
     * @param day        哪天的日志
     * @return 日志数据
     */
    public static JobInstanceLog preLog(Integer jobId, Integer instanceId, String softwareName, Long preStart, Long afterStart,
                                        Integer size, String day, String env) {
        return componentLogger.preLog(jobId, instanceId, softwareName, preStart, afterStart, size, day, env);
    }

    /**
     * 从已经读取的日志里面向后读日志
     *
     * @param instanceId 组件实例ID
     * @param preStart   往前读从哪行开始
     * @param afterStart 往后读从哪行开始
     * @param size       每次多少行日志
     * @param day        哪天的日志
     * @return 日志数据
     */
    public static JobInstanceLog afterLog(Integer jobId, Integer instanceId, String softwareName, Long preStart, Long afterStart,
                                          Integer size, String day, String env) {
        return componentLogger.afterLog(jobId, instanceId, softwareName, preStart, afterStart, size, day, env);
    }

    /**
     * 最新日志读取
     *
     * @param instanceId 实例ID
     * @param size       每次读多少行
     * @param day        读取哪天的日志
     * @return 文件内容
     */
    public static JobInstanceLog readNewestLog(Integer jobId, Integer instanceId, String softwareName, Integer size, String day, String env) {
        return componentLogger.readNewestLog(jobId, instanceId, softwareName, size, day, env);
    }

}
